/*
 * MegaMek -
 * Copyright (C) 2000,2001,2002,2003,2004,2005 Ben Mazur (bmazur@sev.org)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 */

package megamek.client.bot;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.Vector;

import megamek.common.AmmoType;
import megamek.common.BattleArmor;
import megamek.common.Compute;
import megamek.common.Coords;
import megamek.common.Entity;
import megamek.common.EquipmentType;
import megamek.common.IAimingModes;
import megamek.common.IEntityMovementType;
import megamek.common.IGame.Phase;
import megamek.common.IHex;
import megamek.common.Infantry;
import megamek.common.Mech;
import megamek.common.Minefield;
import megamek.common.MiscType;
import megamek.common.Mounted;
import megamek.common.MovePath;
import megamek.common.Protomech;
import megamek.common.TargetRoll;
import megamek.common.Terrains;
import megamek.common.ToHitData;
import megamek.common.WeaponType;
import megamek.common.actions.ChargeAttackAction;
import megamek.common.actions.DfaAttackAction;
import megamek.common.actions.EntityAction;
import megamek.common.actions.TorsoTwistAction;
import megamek.common.actions.WeaponAttackAction;
import megamek.common.containers.PlayerIDandList;
import megamek.common.event.GamePlayerChatEvent;

/**
 * The Class TestBot.
 * @version $Revision: 1.0 $
 */
public class TestBot extends BotClient {

    /** The centities. */
    public CEntity.Table centities = new CEntity.Table(this);

    /** The chatp. */
    protected ChatProcessor chatp = new ChatProcessor();

    /** The ignore. */
    public int ignore = 10;

    /** The debug. */
    public boolean debug = false;

    /** The enemies_moved. */
    public int enemiesMoved = 0;

    /** The old_moves. */
    public GALance oldMoves = null;

    /** The my_mechs_moved. */
    public int myMechsMoved = 0;

    /**
     * Instantiates a new test bot.
     *
     * @param name the name
     * @param host the host
     * @param port the port
     */
    public TestBot(final String name, final String host, final int port) {
        super(name, host, port);
        ignore = config.getIgnoreLevel();
        debug = config.isDebug();
    }

    /* 
     * @see megamek.client.bot.BotClient#initialize()
     */
    public void initialize() {
        // removed
    }

    /* calculatePhysicalTurn method
     * @see megamek.client.bot.BotClient#calculatePhysicalTurn()
     */
    public final PhysicalOption calculatePhysicalTurn() {
        return PhysicalCalculator.calculatePhysicalTurn(this);
    }

    /* calculateMoveTurn() method
     * @see megamek.client.bot.BotClient#calculateMoveTurn()
     */
    public final MovePath calculateMoveTurn() {
        long enter = System.currentTimeMillis();
        int initiative = 0;
        MoveOption min = null;

        System.out.println("beginning movement calculations...");

        // first check and that someone else has moved so we don't replan
        Object[] enemyArray = this.getEnemyEntities().toArray();
        for (int j = 0; j < enemyArray.length; j++) {
            if (!((Entity) enemyArray[j]).isSelectableThisTurn()) {
                initiative++;
            }
        }
        // if nobody's moved and we have a valid move waiting, use that
        if (initiative == enemiesMoved && oldMoves != null) {
            min = this.oldMoves.getResult();
            if (min == null || !min.isMoveLegal() || 
            		(min.isPhysical && 
            				centities.get(min.getPhysicalTargetId()).isPhysicalTarget)) {
                this.oldMoves = null;
                System.out.println("recalculating moves since the old move was invalid");
                return calculateMoveTurn();
            }
        } else {
            enemiesMoved = initiative;
            ArrayList<MoveOption[]> possible = new ArrayList<MoveOption[]>();
            Enumeration<Entity> e = game.getEntities();

            loadEntitities(e);

            Iterator<Entity> i = this.getEntitiesOwned().iterator();
            boolean shortCircuit = false;

            while (i.hasNext() && !shortCircuit) {
                Entity entity = i.next();

                // ignore loaded units
                // (not really necessary unless bot manages to load units)
                if (entity.getPosition() == null) {
                    continue;
                }

                // if we can't move this entity right now, ignore it
                if (!game.getTurn().isValidEntity(entity, game)) {
                    continue;
                }

                CEntity cen = centities.get(entity);
                System.out.println("Contemplating movement of " + entity.getShortName() + " " + entity.getId());

                MoveOption[] result = calculateMove(entity);

                if (game.getOptions().booleanOption("skip_ineligable_movement")
                        && cen.getEntity().isImmobile()) {
                    cen.moved = true;
                } else if (!cen.moved) {
                    if (result.length < 6) {
                        min = result.length > 0 ? result[0] : null;
                        shortCircuit = true;
                    }
                    possible.add(result);
                }
            }

            // should ignore mechs that are not engaged
            // and only do the below when there are 2 or mechs left to move
            if (!shortCircuit) {
                if (this.getEntitiesOwned().size() > 1) {
                    GALance lance = new GALance(this, possible, 50, 80);
                    lance.evolve();
                    min = lance.getResult();
                    this.oldMoves = lance;
                } else if (possible.get(0) != null && possible.get(0).length > 0) {
                    min = possible.get(0)[0];
                }
            }
        }
        if (min == null) {
            min = new MoveOption(game, centities.get(getFirstEntityNum()));
        }
        for (int d = 0; d < enemyArray.length; d++) {
            Entity en = (Entity) enemyArray[d];

            // ignore loaded units
            if (en.getPosition() == null) {
                continue;
            }

            CEntity enemy = centities.get(en);
            int enemyHitArc = CEntity.getThreatHitArc(enemy.current.getFinalCoords(),
                    enemy.current.getFinalFacing(), min.getFinalCoords());
            MoveOption.DamageInfo di = min.damageInfos.get(enemy);
            if (di != null) {
                enemy.expected_damage[enemyHitArc] += di.min_damage;
            }
            if (enemy.expected_damage[enemyHitArc] > 0) {
                enemy.hasTakenDamage = true;
            }
        }
        if (min.isPhysical) {
            centities.get(min.getPhysicalTargetId()).isPhysicalTarget = true;
        }
        System.out.println(min);
        min.getCEntity().current = min;
        min.getCEntity().last = min;
        this.myMechsMoved++;
        min.getCEntity().moved = true;

        long exit = System.currentTimeMillis();
        System.out.println("move turn took " + (exit - enter) + " ms");

        // If this unit has a jammed RAC, and it has only walked,
        // add an unjam action
        if (min.getLastStep() != null) {
            if (min.getCEntity().entity.canUnjamRAC()) {
                unJamAction(min);
            }
        }

        return min;
    }

	/**
	 * Load Entities
	 * @param e
	 */
	public void loadEntitities(final Enumeration<Entity> e) {
		while (e.hasMoreElements()) {
		    Entity entity = e.nextElement();

		    // ignore loaded and off-board units
		    if (entity.getPosition() == null || entity.isOffBoard()) {
		        continue;
		    }

		    CEntity cen = centities.get(entity);
		    cen.refresh();
		    firstPass(cen);
		}
	}

	/**
	 * Un jam action.
	 *
	 * @param min the MoveOption
	 */
	public void unJamAction(final MoveOption min) {
		int movementType = min.getLastStep().getMovementType();
		if ((movementType == IEntityMovementType.MOVE_WALK) || (movementType == IEntityMovementType.MOVE_VTOL_WALK) || (movementType == IEntityMovementType.MOVE_NONE)) {
		    // Cycle through all available weapons, only unjam if the jam(med)
		    // RACs count for a significant portion of possible damage
		    int racDamage = 0;
		    int otherDamage = 0;
		    int clearanceRange = 0;
		    WeaponType testWeapon = new WeaponType();

		    for (Mounted equip : min.getCEntity().entity.getWeaponList()) {
		       
		        testWeapon = (WeaponType) equip.getType();
		        if ((testWeapon.getAmmoType() == AmmoType.T_AC_ROTARY)
		                && equip.isJammed()) {
		            racDamage = racDamage + 4 * (testWeapon.getDamage());
		        } else {
		            if (equip.canFire()) {
		                otherDamage += testWeapon.getDamage();
		                if (testWeapon.getMediumRange() > clearanceRange) {
		                    clearanceRange = testWeapon.getMediumRange();
		                }
		            }
		        }
		    }
		    
		    // Even if the jammed RAC doesn't make up a significant portion of the units damage, its still better to have it functional.
		    // If nothing is "close" then unjam anyways.
		    int checkRange = 100;
		    for (Enumeration<Entity> unitSelection = game.getEntities(); unitSelection.hasMoreElements();) {
		        Entity enemy = unitSelection.nextElement();
		        if ((min.getCEntity().entity.getPosition() != null) && 
		        		(enemy.getPosition() != null) && (enemy.isEnemyOf(min.getCEntity().entity)) && enemy.isVisibleToEnemy()) {
		                checkRange = checkDistance(min, checkRange, enemy);
		        }
		    }
		    if ((racDamage >= otherDamage) || (checkRange < clearanceRange)) {
		        min.addStep(MovePath.STEP_UNJAM_RAC);
		    }
		}
	}

	/**
	 * @param min
	 * @param checkRange
	 * @param enemy
	 * @return
	 */
	public int checkDistance(final MoveOption min, int checkRange1, Entity enemy) {
		int checkRange = checkRange1;
		if (min.getCEntity().entity.getPosition().distance(
		        enemy.getPosition()) < checkRange) {
		    checkRange = min.getCEntity().entity.getPosition().distance(
		            enemy.getPosition());
		}
		return checkRange;
	}

    /**
     * Calculate move.
     *
     * @param entity the entity
     * @return the move option[] 
     * 
     * */
	
	
    public final MoveOption[] calculateMove(final Entity entity) {
        ArrayList<Entity> enemyArray = new ArrayList<Entity>(game.getValidTargets(entity));
        ArrayList<Entity> entities = new ArrayList<Entity>(game.getEntitiesVector());
        CEntity self = centities.get(entity);
        MoveOption[] moveArray;
        int friends = entities.size() - enemyArray.size();

        moveArray = secondPass(self, friends, enemyArray, entities);
        // top balanced
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(1, 1), 50);
        // top damage
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(.5, 1), 50);

        moveArray = thirdPass(self, enemyArray);

        // top balanced
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(1, 1), 30);
        // top damage
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(.5, 1), 30);

        // reduce self threat, and add bonus for terrain
        for (Iterator<MoveOption> i = self.pass.values().iterator(); i.hasNext();) {
            MoveOption option = i.next();
            option.setState();
            option.self_damage *= .5;
            option.self_threat *= .5;
            // Should scale to the unit bv
            double terrain = 2 * ((double) Compute.getTargetTerrainModifier(game,
                    option.getEntity()).getValue());
            if (debug) {
				option.tv.add(terrain + " Terrain Adjusment " + "\n");
			}
            option.self_threat -= terrain;
        }

        moveArray = fourthPass(self, enemyArray);
        // top balanced
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(1, 1), 20);
        // top damage
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(.5, 1), 20);

        // reduce transient damage estimates
        for (Iterator<MoveOption> i = self.pass.values().iterator(); i.hasNext();) {
            MoveOption option = i.next();
            option.self_threat *= .5;
            option.self_damage *= .5;
        }

        moveArray = fifthPass(self, enemyArray);

        /*******************************************************************************************
         * Return top twenty moves to the lance algorithm
         ******************************************************************************************/
        MoveOption[] result = new MoveOption[Math.min(moveArray.length, 20)];
        int offset = 0;
        for (int i = 0; i < Math.min(moveArray.length, 20); i++) {
            MoveOption next = moveArray[i];
            if (next.isPhysical && self.range_damages[CEntity.RANGE_SHORT] > 5 && next.doomed && (offset + 20 < moveArray.length)) {
                    next = moveArray[offset + 20];
                    offset++; 
            }
            result[i] = next;
        }
        return result;
    }

    /*
     * first pass, filter moves based upon present case
     * @param self The CEntity self
     */
    public final void firstPass(final CEntity self) {
        ArrayList<Entity> enemies = getEnemyEntities();
        MoveOption[] moveArray;
        moveArray = assignMoveArrayBasedOnTurn(self);
        System.out.println(self.getEntity().getShortName() + " has " + moveArray.length + " moves");
        for (int i = 0; i < moveArray.length; i++) {
            MoveOption option = moveArray[i];
            option.setState();
            for (int e = 0; e < enemies.size(); e++) { // for each enemy
                Entity en = enemies.get(e);

                // ignore loaded units
                if (en.getPosition() == null) {
                    continue;
                }

                CEntity enemy = centities.get(en);
                int[] modifiers = option.getModifiers(enemy.getEntity());
                if (modifiers[MoveOption.DEFENCE_MOD] == TargetRoll.IMPOSSIBLE && modifiers[MoveOption.ATTACK_MOD] == TargetRoll.IMPOSSIBLE) {
                    continue;
                }
                
                int enemyHitArc = CEntity.getThreatHitArc(enemy.current.getFinalCoords(),enemy.current.getFinalFacing(), option.getFinalCoords());
                int selfHitArc = CEntity.getThreatHitArc(option.getFinalCoords(), option.getFinalFacing(), enemy.current.getFinalCoords());
                
                if (!enemy.getEntity().isImmobile() && modifiers[MoveOption.DEFENCE_MOD] != TargetRoll.IMPOSSIBLE) {
                    self.engaged = true;
                    int mod = modifiers[MoveOption.DEFENCE_MOD];
                    double max = option.getMaxModifiedDamage(enemy.current, mod, modifiers[MoveOption.DEFENCE_PC]);
                    if (en.isSelectableThisTurn()) {
                        enemy.current.addStep(MovePath.STEP_TURN_RIGHT);
                        max = assignMax(option, enemy, modifiers, mod, max);
                        enemy.current.removeLastStep();
                        enemy.current.addStep(MovePath.STEP_TURN_LEFT);
                        max = assignMax(option, enemy, modifiers, mod, max);
                        // return to original facing
                        enemy.current.removeLastStep();
                    }
                    max = self.getThreatUtility(max, selfHitArc);
                    max = checkProneEnity(enemy, max);
                    MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                    di.threat = max;
                    di.max_threat = max;
                    option.threat += max;
                    addToOption(option, e, max, " Threat ");
                }
                /*
                 * As a first approximation, take the maximum to a single target
                 */
                if (!option.isPhysical) {
                    if (modifiers[MoveOption.ATTACK_MOD] != TargetRoll.IMPOSSIBLE) {
                        self.engaged = true;
                        double max = enemy.current.getMaxModifiedDamage(option, modifiers[0], modifiers[MoveOption.ATTACK_PC]);
                        max = enemy.getThreatUtility(max, enemyHitArc);
                        MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                        di.damage = max;
                        di.min_damage = max;
                        addToOption(option, e, max," Damage ");
                        option.damage = Math.max(max, option.damage);
                    }
                } else {
                    CEntity target = centities.get(option.getPhysicalTargetId());
                    try {
                        if (target.getEntity().getId() == enemy.getEntity().getId()) {
                            if (!target.isPhysicalTarget) {
                                ToHitData toHit = null;
                                double selfThreat = 0;
                                double damage = 0;
                                if (option.isJumping() && option.getEntity().canDFA()) {
                                    self.current.setState();
                                    toHit = DfaAttackAction.toHit(game, option.getEntity().getId(),target.getEntity(), option);
                                    boolean targetCheckInstInfantry = target.getEntity() instanceof Infantry;
									boolean targetCheckInstBattleArmor = target.getEntity() instanceof BattleArmor;
									damage = 2 * DfaAttackAction.getDamageFor(option.getEntity(),targetCheckInstInfantry && !targetCheckInstBattleArmor);
                                    
									selfThreat = assignSelfThreat(self, option,
											toHit);
                                } else if (option.getEntity().canCharge()) {
                                    self.current.setState();
                                    toHit = new ChargeAttackAction(option.getEntity(), target
                                            .getEntity()).toHit(game, option);
                                    damage = ChargeAttackAction.getDamageFor(option.getEntity(),
                                            target.getEntity(), false, option.getHexesMoved());
                                    selfThreat = option.getCEntity().getThreatUtility(
                                            ChargeAttackAction.getDamageTakenBy(option.getEntity(),
                                                    target.getEntity()), ToHitData.SIDE_FRONT)
                                            * (Compute.oddsAbove(toHit.getValue()) / 100);
                                    option.setState();
                                }
                             // charging is a good tactic against larger
                                // mechs
                                damage = assignDamage(self, option, enemy,
										target, toHit, damage);
                                MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                                di.damage = damage;
                                di.min_damage = damage;
                                option.damage = damage;
                                option.movement_threat += selfThreat;
                            } else {
                                option.threat += Integer.MAX_VALUE;
                            }
                        }
                    } catch (Exception e1) {
                        e1.printStackTrace();
                        option.threat += Integer.MAX_VALUE;
                    }
                }
            } // -- end while of each enemy
            self.current.setState();
        } // -- end while of first pass
        // top balanced
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(1, 1), 100);
        // top damage
        filterMoves(moveArray, self.pass, new MoveOption.WeightedComparator(.5, 1), 100);
    }

	/**
	 * @param option
	 * @param enemy
	 * @param modifiers
	 * @param mod
	 * @param max
	 * @return
	 */
	public double assignMax(MoveOption option, CEntity enemy, int[] modifiers,
			int mod, double max1) {
		double max = max1;
		max = Math.max(option.getMaxModifiedDamage(enemy.current, mod + 1, modifiers[MoveOption.DEFENCE_PC]), max);
		return max;
	}

	/**
	 * @param self
	 * @param option
	 * @param toHit
	 * @return
	 */
	public double assignSelfThreat(final CEntity self, MoveOption option,
			ToHitData toHit) {
		double selfThreat;
		selfThreat = option.getCEntity().getThreatUtility(
		        DfaAttackAction.getDamageTakenBy(option.getEntity()),
		        ToHitData.SIDE_REAR)
		        * Compute.oddsAbove(toHit.getValue()) / 100;
		selfThreat += option.getCEntity().getThreatUtility(
		        .1 * self.getEntity().getWeight(), ToHitData.SIDE_REAR);
		selfThreat *= 100 / option.getCEntity().getEntity()
		        .getWeight();
		return selfThreat;
	}

	/**
	 * @param option
	 * @param e
	 * @param max
	 * @param value 
	 */
	public void addToOption(MoveOption option, int e, double max, String value) {
		if (debug) {
			option.tv.add(max + value + e + "\n");
		}
	}

	/**
	 * @param enemy
	 * @param max
	 * @return
	 */
	public double checkProneEnity(CEntity enemy, double max1) {
		double max = max1;
		if (enemy.getEntity().isProne()) {
			max *= enemy.base_psr_odds;
		}
		return max;
	}

	/**
	 * @param self
	 * @return
	 */
	public MoveOption[] assignMoveArrayBasedOnTurn(final CEntity self) {
		MoveOption[] moveArray;
		if (self.getEntity().isSelectableThisTurn() && !self.moved) {
            moveArray = self.getAllMoves(this).values().toArray(new MoveOption[0]);
        } else {
            moveArray = new MoveOption[] { self.current };
        }
		return moveArray;
	}

	/**
	 * @param self
	 * @param option
	 * @param enemy
	 * @param target
	 * @param toHit
	 * @param damage
	 * @return
	 */
	public double assignDamage(final CEntity self, MoveOption option,
			CEntity enemy, CEntity target, ToHitData toHit, double damage1) {
		double damage = damage1;
		if (!option.isJumping()) {
			damage *= Math.sqrt((double) enemy.bv / (double) self.bv);
		}
		
		if(toHit!=null){
			damage = target.getThreatUtility(damage, toHit.getSideTable())
		        * Compute.oddsAbove(toHit.getValue()) / 100;
		
		// these are always risky, just don't on 11 or
		// 12
		if (toHit.getValue() > 10) {
			damage = 0;
		}
		// 7 or less is good
		if (toHit.getValue() < 8) {
			damage *= 1.5;
		}}
		// this is all you are good for
		if (self.range_damages[CEntity.RANGE_SHORT] < 5) {
			damage *= 2;
		}
		return damage;
	}

    /**
     * ******************************************************************** 
     * Second pass, combination
     * moves/firing based only on the present case, since only one mech moves at a time
     * ********************************************************************.
     *
     * @param self the CEntity
     * @param friends the friends
     * @param enemy_array The Entity ArrayList
     * @param entities The Entity ArrayList
    
     * @return the move option[] */
    private MoveOption[] secondPass(final CEntity self, final int friends, final ArrayList<Entity> enemy_array,
            final ArrayList<Entity> entities) {
        MoveOption[] moveArray = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();
        for (int j = 0; j < moveArray.length && friends > 2; j++) {
            MoveOption option = moveArray[j];
            for (int e = 0; e < enemy_array.size(); e++) {
                Entity en = enemy_array.get(e);
                CEntity enemy = centities.get(en);
                for (Entity other : entities) {
                    if (other.isEnemyOf(self.entity)) {
                        continue;
                    }
                    MoveOption foption = centities.get(other).current;
                    double threatDivisor = 1;
                    MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                    if (foption.getDamageInfo(enemy, false) != null) {
                        option.damage += (enemy.canMove() ? .1 : .2) * di.damage;
                        threatDivisor += foption.getCEntity().canMove() ? .4 : .6;
                    }
                    option.threat -= di.threat;
                    di.threat /= threatDivisor;
                    option.threat += di.threat;
                }
            }
        }
        return moveArray;
    }

    /**
     * third pass, (not so bad) opportunistic planner gives preference to good ranges/defensive positions based upon the mech
     * characterization.
     * @param self The CEntity self
     * @param enemyArray the enemy_array
    
     * @return the move option[] */
    private MoveOption[] thirdPass(final CEntity self, final ArrayList<Entity> enemyArray) {
        MoveOption[] moveArray = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();

        for (int j = 0; j < moveArray.length; j++) {
            MoveOption option = moveArray[j];
            option.setState();
            double adjustment = 0;
            double tempAdjustment = 0;
            for (int e = 0; e < enemyArray.size(); e++) { // for each enemy
                Entity en = enemyArray.get(e);
                CEntity enemy = centities.get(en);
                int currentRange = self.current.getFinalCoords().distance(
                        enemy.current.getFinalCoords());
                int range = option.getFinalCoords().distance(enemy.current.getFinalCoords());
                tempAdjustment = assignTempAdj(self, option, tempAdjustment,
						enemy, currentRange, range);
                adjustment += Math.sqrt(tempAdjustment * enemy.bv / self.bv);
                // I would always like to face the opponent
                if (!(enemy.getEntity().isProne() || enemy.getEntity().isImmobile())
                        && CEntity.getThreatHitArc(option.getFinalCoords(),
                                option.getFinalFacing(), enemy.getEntity().getPosition()) != ToHitData.SIDE_FRONT) {
                    int firingAngle = CEntity.getFiringAngle(option.getFinalCoords(), option.getFinalFacing(), enemy.getEntity().getPosition());
                    if (firingAngle > 90 && firingAngle < 270) {
                        int distance = option.getFinalCoords().distance(
                                enemy.current.getFinalCoords());
                        double mod = 1;
                        if (firingAngle > 130 && firingAngle < 240) {
							mod = 2;
						}
                        // big formula that says don't do it
                        mod *= ((Math.max(self.jumpMP, .8 * self.runMP) < 5) ? 2 : 1)
                                * ((double) self.bv / (double) 50)
                                * Math.sqrt(((double) self.bv) / enemy.bv)
                                / ((double) distance / 6 + 1);
                        option.self_threat += mod;
                        if (debug) {
							option.tv.add(mod + " " + firingAngle + " Back to enemy\n");
						}
                    }
                }
            }
            adjustment *= self.overall_armor_percent * self.strategy.attack / enemyArray.size();
            // fix for hiding in level 2 water
            // To a greedy bot, it always seems nice to stay in here...
            IHex h = game.getBoard().getHex(option.getFinalCoords());
            if (h.containsTerrain(Terrains.WATER)
                    && h.surface() > (self.getEntity().getElevation() + ((option.getFinalProne()) ? 0
                            : 1))) {
                double mod = (self.getEntity().heat + option.getMovementheatBuildup() <= 7) ? 100
                        : 30;
                adjustment += self.bv / mod;
            }
            // add them in now, then re-add them later
            if (self.range > CEntity.RANGE_SHORT) {
                int elevationDifference = game.getBoard().getHex(option.getFinalCoords()).getElevation()
                        - game.getBoard().getHex(self.current.getFinalCoords()).getElevation();
                adjustment -= (Math.max(elevationDifference, 0) + 1)
                        * ((double) Compute.getTargetTerrainModifier(game, option.getEntity())
                                .getValue() + 1);
            }

            // close the range if nothing else and healthy
            if (option.damage < .25 * self.range_damages[self.range]
                    && adjustment < self.range_damages[self.range]) {
                for (int e = 0; e < enemyArray.size(); e++) {
                    Entity en = enemyArray.get(e);
                    CEntity enemy = centities.get(en);
                    int range = option.getFinalCoords().distance(enemy.current.getFinalCoords());
                    if (range > 5) {
						adjustment += Math.pow(self.overall_armor_percent, 2)
                                * Math.sqrt((double) (range - 4) * enemy.bv / self.bv)
                                / enemyArray.size();
					}
                }
            }

            if (option.damage < .25 * (1 + self.range_damages[self.range])) {
                option.self_threat += 2 * adjustment;
            } else if (option.damage < .5 * (1 + self.range_damages[self.range])) {
                option.self_threat += adjustment;
            }
            if (debug) {
				option.tv.add(option.self_threat + " Initial Damage Adjustment " + "\n");
			}
        }

        return moveArray;
    }

	/**
	 * @param self
	 * @param option
	 * @param tempAdjustment
	 * @param enemy
	 * @param currentRange
	 * @param range
	 * @return
	 */
	public double assignTempAdj(final CEntity self1, MoveOption option1,
			double tempAdjustment1, CEntity enemy1, int currentRange1, int range1) {
		CEntity self = self1;
		MoveOption option = option1;
		double tempAdjustment = tempAdjustment1;
		CEntity enemy = enemy1;
		int range = range1;
		int currentRange = currentRange1;
		
		if (range > self.long_range) {
		    tempAdjustment += (!(range < enemy.long_range) ? .5 : 1)
		            * (1 + self.range_damages[self.range])
		            * (Math.max(range - self.long_range - .5
		                    * Math.max(self.jumpMP, .8 * self.runMP), 0));
		}
		if ((self.range == CEntity.RANGE_SHORT && (currentRange > 5 || range > 9))
		        || (self.range_damages[CEntity.RANGE_SHORT] < 4 && currentRange > 10)) {
		    tempAdjustment += ((enemy.range > CEntity.RANGE_SHORT) ? .5 : 1)
		            * (Math.max(1 + self.range_damages[CEntity.RANGE_SHORT], 5))
		            * Math.max(range - .5 * Math.max(self.jumpMP, .8 * self.runMP), 0);
		} else if (self.range == CEntity.RANGE_MEDIUM) {
		    tempAdjustment += ((currentRange < 6 || currentRange > 12) ? 1 : .25)
		            * ((enemy.range > CEntity.RANGE_SHORT) ? .5 : 1)
		            * (1 + self.range_damages[CEntity.RANGE_MEDIUM])
		            * Math.abs(range - .5 * Math.max(self.jumpMP, .8 * self.runMP));
		} else if (option.damage < .25 * self.range_damages[CEntity.RANGE_LONG]) {
		    tempAdjustment += ((range < 10) ? .25 : 1)
		            * (Math.max(1 + self.range_damages[CEntity.RANGE_LONG], 3))
		            * (1 / (1 + option.threat));
		}
		return tempAdjustment;
	}

    // pass should contains 30 ~ 60
    /**
     * ******************************************************************** fourth pass, speculation
     * on top moves use averaging to filter
     * ********************************************************************.
     *
     * @param self the self
     * @param enemyArray the enemy_array
    
     * @return the move option[] */
    private MoveOption[] fourthPass(final CEntity self, final ArrayList<Entity> enemyArray) {
        MoveOption[] moveArray = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();
        for (int e = 0; e < enemyArray.size(); e++) { // for each enemy
            Entity en = enemyArray.get(e);
            CEntity enemy = centities.get(en);
            // engage in speculation on "best choices" when you loose iniative
            if (enemy.canMove()) {
                ArrayList<MoveOption> enemy_move_array = enemy.pass.getArray();
                ArrayList<MoveOption> to_check = new ArrayList<MoveOption>();
                // check some enemy moves
                for (int j = 0; j < moveArray.length; j++) {
                    MoveOption option = null;
                    to_check.clear();
                    option = moveArray[j];
                    option.setState();
                    // check for damning hexes specifically
                    // could also look at intervening defensive
                    ArrayList<Coords> coord = new ArrayList<Coords>();
                    int finalFacing = option.getFinalFacing();
					Coords back = option.getFinalCoords().translated((finalFacing + 3) % 6);
                    assignCoord(option, coord, finalFacing, back);
                    Iterator<Coords> ci = coord.iterator();
                    while (ci.hasNext()) {
                        Coords test = ci.next();
                        List<MoveOption> c = enemy.findMoves(test, this);
                        if (c.isEmpty()) {
							to_check.addAll(c);
						}
                    }
                    int range = option.getFinalCoords().distance(enemy.current.getFinalCoords());
                    int compare = 0;
                    compare = assignCompare(enemy, range, compare);
                    double mod = this.enemiesMoved / this.getEnemyEntities().size();
                    compare *= (1 + mod);
                    for (int k = 0; k <= compare && k < enemy_move_array.size(); k++) {
                        if (enemy_move_array.size() < compare) {
                            to_check.add(enemy_move_array.get(k));
                        } else {
                            int value = Compute.randomInt(enemy_move_array.size());
                            assignToChkBasedonValue(enemy_move_array, to_check,
									k, value);
                        }
                    }
                    Iterator<MoveOption> eo = to_check.iterator();
                    while (eo.hasNext()) {
                        MoveOption enemyOption = eo.next();
                        double max_threat = 0;
                        double maxDamage = 0;
                        enemyOption.setState();
                        int enemyHitArc = CEntity.getThreatHitArc(enemyOption.getFinalCoords(),
                                enemyOption.getFinalFacing(), option.getFinalCoords());
                        int selfHitArc = CEntity.getThreatHitArc(enemyOption.getFinalCoords(),
                                enemyOption.getFinalFacing(), option.getFinalCoords());
                        if (enemyOption.isJumping()) {
                            enemyHitArc = Compute.ARC_FORWARD;
                        }
                        int[] modifiers = option.getModifiers(enemyOption.getEntity());
                        max_threat = assignMaxThreat(self, enemy, option,
								enemyOption, max_threat, selfHitArc,
								modifiers);
                        if (modifiers[0] != TargetRoll.IMPOSSIBLE) {
                            self.engaged = true;
                            maxDamage = assignMaxDamage(enemy, option,
									enemyOption, enemyHitArc, modifiers);
                        }
                        MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                        di.max_threat = Math.max(max_threat, di.max_threat);
                        di.min_damage = Math.min(di.min_damage, maxDamage);
                        if (max_threat - maxDamage > di.threat - di.damage) {
                            di.threat = max_threat;
                            di.damage = maxDamage;
                            if (debug) {
                                option.tv.add(max_threat + " Spec Threat " + e + "\n");
                                option.tv.add(maxDamage + " Spec Damage " + e + "\n");
                            }
                        }
                    }
                    // update estimates
                    option.damage = 0;
                    option.threat = 0;
                    for (Iterator<CEntity> i = option.damageInfos.keySet().iterator(); i.hasNext();) {
                        // my damage is the average of expected and min
                        CEntity cen = i.next();
                        // rescale
                        MoveOption.DamageInfo di = option.getDamageInfo(cen, true);
                        di.min_damage /= cen.strategy.target;
                        di.damage /= cen.strategy.target;
                        option.damage += (di.min_damage + di.damage) / 2;

                        // my threat is average of absolute worst, and expected
                        option.threat = Math.max(option.threat, di.max_threat + di.threat) / 2;
                        di.threat = (di.max_threat + 2 * di.threat) / 3;
                    }
                }
                // restore enemy
                enemy.current.setState();
            }
            self.current.setState();
        } // --end move speculation
        return moveArray;
    }

	/**
	 * @param enemy
	 * @param option
	 * @param enemyOption
	 * @param enemyHitArc
	 * @param modifiers
	 * @return
	 */
	public double assignMaxDamage(CEntity enemy, MoveOption option,
			MoveOption enemyOption, int enemyHitArc, int[] modifiers) {
		double maxDamage;
		maxDamage = enemyOption.getMaxModifiedDamage(option, modifiers[0],
		        modifiers[MoveOption.ATTACK_PC]);
		maxDamage = enemy.getThreatUtility(maxDamage, enemyHitArc);
		if (option.isPhysical) {
		    maxDamage = assignMaxDamage(enemy, option);
		}
		return maxDamage;
	}

	/**
	 * @param enemy
	 * @param option
	 * @return
	 */
	public double assignMaxDamage(CEntity enemy, MoveOption option) {
		double max_damage;
		if (centities.get(option.getPhysicalTargetId()).getEntity().getId() == enemy
		        .getEntity().getId()) {
		    max_damage = option.getDamage(enemy);
		} else {
		    max_damage = 0;
		}
		return max_damage;
	}

	/**
	 * @param self
	 * @param enemy
	 * @param option
	 * @param enemy_option
	 * @param max_threat
	 * @param self_hit_arc
	 * @param modifiers
	 * @return
	 */
	public double assignMaxThreat(final CEntity self, CEntity enemy,
			MoveOption option, MoveOption enemy_option, double max_threat,
			int self_hit_arc, int[] modifiers) {
		if (modifiers[1] != TargetRoll.IMPOSSIBLE) {
		    self.engaged = true;
		    if (!enemy_option.isJumping()) {
		        max_threat = option.getMaxModifiedDamage(enemy_option,
		                modifiers[1], modifiers[MoveOption.DEFENCE_PC]);
		    } else {
		        max_threat = .8 * enemy.getModifiedDamage(
		                (modifiers[MoveOption.DEFENCE_PC] == 1) ? CEntity.TT
		                        : ToHitData.SIDE_FRONT,
		                enemy_option.getFinalCoords().distance(
		                        option.getFinalCoords()), modifiers[1]);
		    }
		    max_threat = self.getThreatUtility(max_threat, self_hit_arc);
		}
		return max_threat;
	}

	/**
	 * @param enemy_move_array
	 * @param to_check
	 * @param k
	 * @param value
	 */
	public void assignToChkBasedonValue(ArrayList<MoveOption> enemy_move_array,
			ArrayList<MoveOption> to_check, int k, int value) {
		if (value % 2 == 1) {
		    to_check.add(enemy_move_array.get(value));
		} else {
		    to_check.add(enemy_move_array.get(k));
		}
	}

	/**
	 * @param option
	 * @param coord
	 * @param finalFacing
	 * @param back
	 */
	public void assignCoord(MoveOption option, ArrayList<Coords> coord,
			int finalFacing, Coords back) {
		coord.add(back);
		coord.add(back.translated((finalFacing + 2) % 6));
		coord.add(back.translated((finalFacing + 4) % 6));
		coord.add(option.getFinalCoords().translated(finalFacing));
		coord.add(option.getFinalCoords().translated((finalFacing + 1) % 6));
		coord.add(option.getFinalCoords().translated((finalFacing + 2) % 6));
		coord.add(option.getFinalCoords().translated((finalFacing + 4) % 6));
		coord.add(option.getFinalCoords().translated((finalFacing + 5) % 6));
	}

	/**
	 * @param enemy
	 * @param range
	 * @param compare
	 * @return
	 */
	public int assignCompare(CEntity enemy, int range, int compare) {
		if ((enemy.long_range) > range - Math.max(enemy.jumpMP, enemy.runMP)) {
		    compare = 30;
		} else if (enemy.long_range > range) {
		    compare = 10;
		}
		return compare;
	}

    // pass should now be 20 ~ 40
    /**
     * fifth pass, final damage and threat approximation --prevents moves that from the previous pass would cause the mech to die 
     *
     * @param self the self
     * @param enemy_array the enemy_array
    
     * @return the move option[] */
    private MoveOption[] fifthPass(final CEntity self, final ArrayList<Entity> enemy_array) {
        MoveOption[] moveArray = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();

        if (self.engaged) {
            for (int j = 0; j < moveArray.length; j++) {
                MoveOption option = moveArray[j];
                option.setState();
                GAAttack temp = this.bestAttack(option);
                if (temp != null) {
                    option.damage = (option.damage + temp.getFittestChromosomesFitness()) / 2;
                } else {
                    option.damage /= 2;
                }
                for (int e = 0; e < enemy_array.size(); e++) { // for each
                    // enemy
                    Entity en = enemy_array.get(e);
                    CEntity enemy = centities.get(en);
                    if (!enemy.canMove()) {
                        option.setThreat(enemy, (option.getThreat(enemy) + this.attackUtility(
                                enemy.current, self)) / 2);
                        if (debug) {
							option.tv.add(option.getThreat(enemy) + " Revised Threat " + e + " \n");
						}
                        if (!option.isPhysical) {
                            if (temp != null) {
                                option.setDamage(enemy, (option.getDamage(enemy) + temp
                                        .getDamageUtility(enemy)) / 2);
                            } else {
                                // probably zero, but just in case
                                option.setDamage(enemy, option.getMinDamage(enemy));
                            }
                            if (debug) {
								option.tv.add(option.getDamage(enemy) + " Revised Damage " + e
                                        + " \n");
							}
                            // this needs to be reworked
                            if (option.getFinalCoords().distance(enemy.current.getFinalCoords()) == 1) {
                                PhysicalOption p = PhysicalCalculator.getBestPhysicalAttack(option
                                        .getEntity(), enemy.getEntity(), game);
                                if (p != null) {
                                    option.setDamage(enemy, option.getDamage(enemy)
                                                    + p.expectedDmg);
                                    if (debug) {
										option.tv.add(p.expectedDmg + " Physical Damage " + e
                                                + " \n");
									}
                                }
                                p = PhysicalCalculator.getBestPhysicalAttack(enemy.getEntity(),
                                        option.getEntity(), game);
                                if (p != null) {
                                    option.setThreat(enemy, option.getThreat(enemy) + .5
                                            * p.expectedDmg);
                                    if (debug) {
										option.tv.add(.5 * p.expectedDmg + " Physical Threat " + e
                                                + " \n");
									}
                                }
                            }
                        }
                    } else if (!option.isPhysical) { // enemy can move (not
                        if (temp != null) {
                            option.setDamage(enemy, (2 * option.getDamage(enemy) + temp
                                    .getDamageUtility(enemy)) / 3);
                        } else {
                            option.setDamage(enemy, option.getMinDamage(enemy));
                        }
                    } else {
                        // get a more accurate estimate
                        option.setDamage(enemy, option.getDamage(enemy)
                                / Math.sqrt((double) enemy.bv / (double) self.bv));
                        option.damage = option.getDamage(enemy);
                    }
                }
                option.threat = 0;
                for (Iterator<MoveOption.DamageInfo> i = option.damageInfos.values().iterator(); i
                        .hasNext();) {
                    option.threat += i.next().threat;
                }
                if (debug) {
                    option.tv.add(option.threat + " Revised Threat Utility\n");
                    option.tv.add(option.damage + " Revised Damage Utility\n");
                }
            }
        }
        Arrays.<MoveOption> sort(moveArray, new MoveOption.WeightedComparator(1, 1));
        self.current.setState();

        return moveArray;
        
    }

    /**
     * Filter moves.
     *
     * @param moveArray the moveArray
     * @param pass the pass
     * @param comp the MoveOption Weighted comparator
     * @param filter the filter
     */
    private void filterMoves(final MoveOption[] moveArray, final MoveOption.Table pass,
            final MoveOption.WeightedComparator comp, final int filter) {
        Arrays.sort(moveArray, comp);

        // top 100 utility, mostly conservative
        for (int i = 0; i < filter && i < moveArray.length; i++) {
            pass.put(moveArray[i]);
        }
    }

    /* 
     * @see megamek.client.bot.BotClient#initFiring()
     */
    protected final void initFiring() {
        ArrayList<Entity> entities = new ArrayList<Entity>(game.getEntitiesVector());
        for (int i = 0; i < entities.size(); i++) {
            Entity entity = entities.get(i);
            CEntity centity = centities.get(entity);
            centity.reset();
            centity.enemy_num = i;
        }
        for (Iterator<Entity> i = this.getEnemyEntities().iterator(); i.hasNext();) {
            Entity entity = i.next();
            CEntity centity = centities.get(entity);
            if (entity.isMakingDfa() || entity.isCharging()) {
                // try to prevent a physical attack from happening
                // but should take into account the toHit of the attack
                centity.strategy.target = 2.5;
            }
        }
    }

    /**
     * Calculate weapon attacks.
     *
     * @param entity the entity
     * @param mountedWeapon the mountedWeapon
     * @param bestOnly the bestOnly
    
     * @return the array list */
    protected final ArrayList<AttackOption> calculateWeaponAttacks(final Entity entity, final Mounted mountedWeapon,
            final boolean bestOnly) {
        int from = entity.getId();
        int weaponID = entity.getEquipmentNum(mountedWeapon);
        int spinMode = 0;
        int stargMod;
        ArrayList<AttackOption> result = new ArrayList<AttackOption>();
        Enumeration<Entity> entities = game.getValidTargets(entity).elements();
        WeaponAttackAction wep_test;
        WeaponType spinner;
        AttackOption a = null;
        AttackOption max = new AttackOption(null, null, 0, null, 1);
        while (entities.hasMoreElements()) {
            Entity e = entities.nextElement();
            CEntity enemy = centities.get(e);
            // long entry = System.currentTimeMillis();
            ToHitData th = WeaponAttackAction.toHit(game, from, e, weaponID);
            // long exit = System.currentTimeMillis();
            // if (exit != entry)
            // System.out.println("Weapon attack toHit took "+(exit-entry));
            if (th.getValue() != TargetRoll.IMPOSSIBLE && !(th.getValue() >= 13)) {
                double expectedDmg;

                wep_test = new WeaponAttackAction(from, e.getId(), weaponID);

                // If this is an Ultra or Rotary cannon, check for spin up
                spinner = (WeaponType) mountedWeapon.getType();
                if ((spinner.getAmmoType() == AmmoType.T_AC_ULTRA)
                        || (spinner.getAmmoType() == AmmoType.T_AC_ULTRA_THB)
                        || (spinner.getAmmoType() == AmmoType.T_AC_ROTARY)) {
                    spinMode = Compute.spinUpCannon(game, wep_test);
                    super.sendModeChange(from, weaponID, spinMode);
                }

                // Ammo cycler runs each valid ammo type through the weapon
                // while
                // calling for expected damage on each type; best type by damage
                // is loaded

                expectedDmg = Compute.getAmmoAdjDamage(game, wep_test);

                // Get the secondary target modifier for this weapon/target
                // combo

                stargMod = 1;

                if (entity.getFacing() != -1) {
                    if (entity.canChangeSecondaryFacing()) {

                        if (!Compute.isInArc(entity.getPosition(), entity.getSecondaryFacing(), e
                                .getPosition(), Compute.ARC_FORWARD)) {
                            stargMod = 2;
                        }
                    } else {
                        if (!Compute.isInArc(entity.getPosition(), entity.getFacing(), e.getPosition(),
                                Compute.ARC_FORWARD)) {
                            stargMod = 2;
                        }

                    }
                }

                // If the attacker is Mech, it may mount a specialty targeting
                // system
                if (entity instanceof Mech) {
                    // Multi-trac I/II have no secondary target penalties
                    if (entity.getTargSysType() == MiscType.T_TARGSYS_MULTI_TRAC
                            | entity.getTargSysType() == MiscType.T_TARGSYS_MULTI_TRAC_II) {
                        stargMod = 0;
                    }
                }

                // For good measure, infantry cannot attack multiple targets
                if (entity instanceof Infantry && !(entity instanceof BattleArmor)) {
                    stargMod = 13;
                }

                a = new AttackOption(enemy, mountedWeapon, expectedDmg, th, stargMod);
                if (a.value > max.value) {
                    if (bestOnly) {
                        max = a;
                    } else {
                        result.add(0, a);
                    }
                } else {
                    result.add(a);
                }
            }
        }
        if (bestOnly && max.target != null) {
            result.add(max);
        }
        if (result.size() > 0) {
            result.add(new AttackOption(null, mountedWeapon, 0, null, 1));
        }
        return result;
    }

    /**
     * Best attack.
     *
     * @param es the es
    
     * @return the gA attack */
    public final GAAttack bestAttack(final MoveOption es) {
        return bestAttack(es, null, 2);
    }

    /**
     * Best attack.
     *
     * @param es the es
     * @param target the target
     * @param searchLevel the search_level
     *
     * @return the gA attack */
    public final GAAttack bestAttack(final MoveOption es, final CEntity target, final int searchLevel) {
        Entity entity = es.getEntity();
        int[] attacks = new int[4];
        ArrayList<AttackOption> c = new ArrayList<AttackOption>();
        ArrayList<ArrayList<AttackOption>> front = new ArrayList<ArrayList<AttackOption>>();
        ArrayList<ArrayList<AttackOption>> left = new ArrayList<ArrayList<AttackOption>>();
        ArrayList<ArrayList<AttackOption>> right = new ArrayList<ArrayList<AttackOption>>();
        ArrayList<ArrayList<AttackOption>> rear = new ArrayList<ArrayList<AttackOption>>();
        GAAttack result = null;
        int oFacing = entity.getFacing();
        double frontLaDmg = 0;
        double frontRaDmg = 0;
        double leftLaDmg = 0;
        double leftRaDmg = 0;
        double rightLaDmg = 0;
        double rightRaDmg = 0;
        PhysicalOption bestFrontPo = new PhysicalOption(entity);
        PhysicalOption bestLeftPo = new PhysicalOption(entity);
        PhysicalOption bestRightPo = new PhysicalOption(entity);

        // Get best physical attack
        for (Mounted mountedWeapon : entity.getWeaponList()) {

            // If this weapon is in the same arm as a
            // brush off attack skip to next weapon.
            c = this.calculateWeaponAttacks(entity, mountedWeapon, true);

            // Get best physical attack
            bestFrontPo = PhysicalCalculator.getBestPhysical(entity, game);

            if ((bestFrontPo != null) && (entity instanceof Mech)) {

                // If this weapon is in the same arm as a brush off attack
                // skip to next weapon

                if (((bestFrontPo.type == PhysicalOption.BRUSH_LEFT) || (bestFrontPo.type == PhysicalOption.BRUSH_BOTH))
                        && (mountedWeapon.getLocation() == Mech.LOC_LARM)) {
                    continue;
                }
                if (((bestFrontPo.type == PhysicalOption.BRUSH_RIGHT) || (bestFrontPo.type == PhysicalOption.BRUSH_BOTH))
                        && (mountedWeapon.getLocation() == Mech.LOC_RARM)) {
                    continue;
                }

                // Total the damage of all weapons fired from each arm
                if (((bestFrontPo.type == PhysicalOption.PUNCH_LEFT) || (bestFrontPo.type == PhysicalOption.PUNCH_BOTH))
                        && (mountedWeapon.getLocation() == Mech.LOC_LARM)) {
                    if (c.isEmpty()) {
                        frontLaDmg += c.get(c.size() - 2).value;
                    }
                }
                if (((bestFrontPo.type == PhysicalOption.PUNCH_RIGHT) || (bestFrontPo.type == PhysicalOption.PUNCH_BOTH))
                        && (mountedWeapon.getLocation() == Mech.LOC_RARM)) {
                    if (c.isEmpty()) {
                        frontRaDmg += c.get(c.size() - 2).value;
                    }
                }
                // If this weapon is a push attack and an arm mounted
                // weapon skip to next weapon

                if ((bestFrontPo.type == PhysicalOption.PUSH_ATTACK)
                        && ((mountedWeapon.getLocation() == Mech.LOC_LARM) || (mountedWeapon.getLocation() == Mech.LOC_RARM))) {
                    continue;
                }
            }

            // If this weapon is in the same arm as a punch
            // attack, add the damage to the running total.
            if (c.isEmpty()) {
                front.add(c);
                attacks[0] = Math.max(attacks[0], c.size());
            }
            if (!es.getFinalProne() && entity.canChangeSecondaryFacing()) {
                entity.setSecondaryFacing((oFacing + 5) % 6);
                c = this.calculateWeaponAttacks(entity, mountedWeapon, true);
                if (c.isEmpty()) {
                    // Get best physical attack
                    bestLeftPo = PhysicalCalculator.getBestPhysical(entity, game);
                    if ((bestLeftPo != null) && (entity instanceof Mech)) {
                        if (((bestLeftPo.type == PhysicalOption.PUNCH_LEFT) || (bestLeftPo.type == PhysicalOption.PUNCH_BOTH))
                                && (mountedWeapon.getLocation() == Mech.LOC_LARM)) {
                            leftLaDmg += c.get(c.size() - 2).value;
                        }
                        if (((bestLeftPo.type == PhysicalOption.PUNCH_RIGHT) || (bestLeftPo.type == PhysicalOption.PUNCH_BOTH))
                                && (mountedWeapon.getLocation() == Mech.LOC_RARM)) {
                            leftRaDmg += c.get(c.size() - 2).value;
                        }
                    }
                    left.add(c);
                    attacks[1] = Math.max(attacks[1], c.size());
                }
                entity.setSecondaryFacing((oFacing + 1) % 6);
                c = this.calculateWeaponAttacks(entity, mountedWeapon, true);
                if (c.isEmpty()) {
                    // Get best physical attack
                    bestRightPo = PhysicalCalculator.getBestPhysical(entity, game);
                    if ((bestRightPo != null) && (entity instanceof Mech)) {
                        if (((bestRightPo.type == PhysicalOption.PUNCH_LEFT) || (bestRightPo.type == PhysicalOption.PUNCH_BOTH))
                                && (mountedWeapon.getLocation() == Mech.LOC_LARM)) {
                            rightLaDmg += c.get(c.size() - 2).value;
                        }
                        if (((bestRightPo.type == PhysicalOption.PUNCH_RIGHT) || (bestRightPo.type == PhysicalOption.PUNCH_BOTH))
                                && (mountedWeapon.getLocation() == Mech.LOC_RARM)) {
                            rightRaDmg += c.get(c.size() - 2).value;
                        }
                    }
                    right.add(c);
                    attacks[2] = Math.max(attacks[2], c.size());
                }
                entity.setSecondaryFacing((oFacing + 3) % 6);
                c = this.calculateWeaponAttacks(entity, mountedWeapon, true);
                if (c.isEmpty()) {
                    rear.add(c);
                    attacks[3] = Math.max(attacks[3], c.size());
                }
            } else {
                attacks[1] = 0;
                attacks[2] = 0;
            }
            entity.setSecondaryFacing(oFacing);
        }

        fireOrPhysicalCheck(bestFrontPo, entity, front, frontLaDmg, frontRaDmg);

        ArrayList<ArrayList<ArrayList<AttackOption>>> arcs = new ArrayList<ArrayList<ArrayList<AttackOption>>>();
        arcs.add(front);
        if (!es.getFinalProne() && entity.canChangeSecondaryFacing()) {
            fireOrPhysicalCheck(bestLeftPo, entity,
            		left, leftLaDmg, leftRaDmg);
            arcs.add(left);
            fireOrPhysicalCheck(bestRightPo, entity, right, rightLaDmg, rightRaDmg);
            arcs.add(right);
            // Meks and protos can't twist all the way around.
            if (!(entity instanceof Mech) && !(entity instanceof Protomech)) {
                arcs.add(rear);
            }
        }
        for (int i = 0; i < arcs.size(); i++) {
            ArrayList<ArrayList<AttackOption>> v = arcs.get(i);
            result = setResult(target, searchLevel, entity, attacks, result,
					i, v);
        }
        return result;
    }

	/**
	 * @param target
	 * @param search_level
	 * @param entity
	 * @param attacks
	 * @param result
	 * @param i
	 * @param v
	 * 
	 * @return result
	 */
	public GAAttack setResult(final CEntity target, final int search_level,
			Entity entity, int[] attacks1, GAAttack result, int i, // NOPMD by Poornima on 5/15/12 7:22 PM
			ArrayList<ArrayList<AttackOption>> v) {
		 CEntity targetVal = target;
		 Entity entity1 = entity;
		ArrayList<ArrayList<AttackOption>> attackOption = v;
		int searchLevel = search_level;
		int index = i;
		GAAttack resultVal = result;
		int[] attacks = attacks1;
		if (attackOption.size() > 0) {
		    GAAttack test = new GAAttack(this, centities.get(entity1), attackOption, Math.max(
		            (attackOption.size() + attacks[index]) * searchLevel, 20 * searchLevel),
		            30 * searchLevel, entity1.isEnemyOf(getEntitiesOwned().get(0)));
		    test.setFiringArc(index);
		    test.evolve();
		    if (targetVal != null) {
		        if (resultVal == null
		                || test.getDamageUtility(targetVal) > resultVal.getDamageUtility(targetVal)) {
		            resultVal = test;
		        }
		    } else if (resultVal == null
		            || test.getFittestChromosomesFitness() > resultVal
		                    .getFittestChromosomesFitness()) {
		        resultVal = test;
		    }
		}
		return resultVal;
	}

    /**
     * If the best attack is a punch, then check each punch damage against the weapons damage from
     * the appropriate arm; if the punch does more damage, drop the weapons in that arm to 0
     * expected damage Repeat this for left and right twists.
     *
     * @param bestPO the best_po
     * @param entity the entity
     * @param attackOptions the attack options
     * @param laDmg the la_dmg
     * @param raDmg the ra_dmg
     */
    private void fireOrPhysicalCheck(final PhysicalOption bestPO, final Entity entity,
            final ArrayList<ArrayList<AttackOption>> attackOptions, final double laDmg, final double raDmg) {
        ArrayList<AttackOption> c;
        if ((bestPO != null) && (entity instanceof Mech)) {
            if (bestPO.type == PhysicalOption.PUNCH_LEFT) {
                if ((laDmg < bestPO.expectedDmg) && (attackOptions.size() > 0)) {
                    for (int i = 0; i < attackOptions.size(); i++) {
                        c = attackOptions.get(i);
                        for (int j = 0; j < c.size(); j++) {
                            if (c.get(j).weapon.getLocation() == Mech.LOC_LARM) {
                                c.get(j).expected = 0;
                                c.get(j).primary_expected = 0;
                            }
                        }
                    }
                }
            }
            if (bestPO.type == PhysicalOption.PUNCH_RIGHT) {
                if ((raDmg < bestPO.expectedDmg) && (attackOptions.size() > 0)) {
                    for (int i = 0; i < attackOptions.size(); i++) {
                        c = attackOptions.get(i);
                        for (int j = 0; j < c.size(); j++) {
                            if (c.get(j).weapon.getLocation() == Mech.LOC_RARM) {
                                c.get(j).expected = 0;
                                c.get(j).primary_expected = 0;
                            }
                        }
                    }
                }
            }
            if (bestPO.type == PhysicalOption.PUNCH_BOTH) {
                if (((laDmg + raDmg) < bestPO.expectedDmg) && (attackOptions.size() > 0)) {
                    for (int i = 0; i < attackOptions.size(); i++) {
                        c = attackOptions.get(i);
                        for (int j = 0; j < c.size(); j++) {
                            if (c.get(j).weapon.getLocation() == Mech.LOC_LARM) {
                                c.get(j).expected = 0;
                                c.get(j).primary_expected = 0;
                            }
                            if (c.get(j).weapon.getLocation() == Mech.LOC_RARM) {
                                c.get(j).expected = 0;
                                c.get(j).primary_expected = 0;
                            }
                        }
                    }
                }
            }
        }
    }

    /* could use best of best strategy instead of expensive ga */
    /**
     * Attack utility.
     *
     * @param es the es
     * @param target the target
     *
     * @return the double */
    public final double attackUtility(final MoveOption es, final CEntity target) {
        GAAttack result = bestAttack(es, target, 1);
        if (result == null){ 
            return 0;
        }
        else{ 
        	return result.getFittestChromosomesFitness();
        }
    }

    /*
     * @see megamek.client.bot.BotClient#calculateFiringTurn()
     */
    public final void calculateFiringTurn() {
        int firstEntity = game.getFirstEntityNum(getMyTurn());
        int entityNum = firstEntity;
        int best_entity = firstEntity;
        int spinMode = 0;
        double max = java.lang.Double.NEGATIVE_INFINITY;
        int[] results = null;
        ArrayList<ArrayList<AttackOption>> winner = null;
        int arc = 0;
        WeaponType spinner;

        if (entityNum == -1) {
            return;
        }

        do {
            Entity entity = game.getEntity(entityNum);
            CEntity centity = centities.get(entity);

            GAAttack test = bestAttack(centity.current, null, 3);

            if (test != null && test.getFittestChromosomesFitness() > max) {
                max = test.getFittestChromosomesFitness();
                results = test.getResultChromosome();
                arc = test.getFiringArc();
                best_entity = entityNum;
                winner = test.getAttack();
            }
            entityNum = game.getNextEntityNum(getMyTurn(), entityNum);
        } while (entityNum != firstEntity && entityNum != -1);

        Vector<EntityAction> entityActionVector = new Vector<EntityAction>();
        // maximum already selected (or default)
        Entity bestEntity = game.getEntity(best_entity);
        if (results != null) {
            Entity primaryTarget = game.getEntitiesVector().get(results[results.length - 1]);
            TreeSet<AttackOption> treeSetAttackOption = new TreeSet<AttackOption>(new AttackOption.Sorter(centities
                    .get(primaryTarget)));
            for (int i = 0; i < results.length - 1; i++) {
                if (winner != null) {
                    AttackOption a = winner.get(i).get(results[i]);
                    if (a.target != null) {
                        a.target.expected_damage[a.toHit.getSideTable()] += a.value;
                        a.target.hasTakenDamage = true;
                        treeSetAttackOption.add(a);
                    }
                }
            }
            Iterator<AttackOption> i = treeSetAttackOption.iterator();
            while (i.hasNext()) {
                AttackOption a = i.next();

                WeaponAttackAction newAttack = new WeaponAttackAction(bestEntity.getId(), a.target
                        .getEntity().getId(), bestEntity.getEquipmentNum(a.weapon));

                if (bestEntity.getEquipment(newAttack.getWeaponId()).getLinked() != null) {
                    spinner = (WeaponType) a.weapon.getType();

                    // If this is an ultra-cannon or rotary cannon, try to spin
                    // it up

                    if ((spinner.getAmmoType() == AmmoType.T_AC_ULTRA)
                            || (spinner.getAmmoType() == AmmoType.T_AC_ULTRA_THB)
                            || (spinner.getAmmoType() == AmmoType.T_AC_ROTARY)) {
                        spinMode = Compute.spinUpCannon(game, newAttack);
                        super.sendModeChange(bestEntity.getId(), bestEntity.getEquipmentNum(a.weapon), spinMode);
                    }
                    Mounted cur_ammo = bestEntity.getEquipment(newAttack.getWeaponId()).getLinked();
                    newAttack.setAmmoId(bestEntity.getEquipmentNum(cur_ammo));
                    Compute.getAmmoAdjDamage(game, newAttack);

                }
                entityActionVector.add(newAttack);

            }

            // Use the attack options and weapon attack actions to determine the
            // best aiming point

            if (entityActionVector.size() > 0) {
                getAimPoint(treeSetAttackOption, entityActionVector);
            }

        }
        switch (arc) {
        case 1:
            entityActionVector.add(0, new TorsoTwistAction(bestEntity.getId(), (bestEntity.getFacing() + 5) % 6));
            break;
        case 2:
            entityActionVector.add(0, new TorsoTwistAction(bestEntity.getId(), (bestEntity.getFacing() + 1) % 6));
            break;
        case 3:
            entityActionVector.add(0, new TorsoTwistAction(bestEntity.getId(), (bestEntity.getFacing() + 3) % 6));
            break;
		default:
			break;
        }
        sendAttackData(best_entity, entityActionVector);
    }

    /**
     * consider how to put more pre-turn logic here.
     */
    protected final void initMovement() {
        this.myMechsMoved = 0;
        this.oldMoves = null;
        this.enemiesMoved = 0;
        double maxModifier = 1.4;
        ArrayList<Entity> entities = new ArrayList<Entity>(game.getEntitiesVector());
        double numEntities = Math.sqrt(entities.size()) / 100;
        ArrayList<CEntity> friends = new ArrayList<CEntity>();
        ArrayList<CEntity> foes = new ArrayList<CEntity>();
        double friendSum = 0;
        double foeSum = 0;
        double maxFoeBv = 0;
        CEntity maxFoe = null;
        for (int i = 0; i < entities.size(); i++) {
            Entity entity = entities.get(i);
            CEntity centity = centities.get(entity);
            centity.enemy_num = i;
            double oldValue = centity.bv * (centity.overall_armor_percent + 1);
            centity.reset(); 
            // should get fresh values
            double newValue = centity.bv * (centity.overall_armor_percent + 1);
            double percent = 1 + (newValue - oldValue) / oldValue;
            if (entity.getOwner().equals(getLocalPlayer())) {
                friends.add(centity);
                friendSum += newValue;
                if (percent < .85) {
                    // small retreat
                    centity.strategy.attack = .85;
                } else if (percent < .95) {
                    centity.strategy.attack = 1;
                } else if (percent <= 1 && centity.strategy.attack < maxModifier) {
                    if (percent == 1) {
                        assignStrAttack(numEntities, centity);
                    } else {
                        centity.strategy.attack *= (1.0 + 2 * numEntities);
                    }
                }
            } else if (!entity.getOwner().isEnemyOf(getLocalPlayer())) {
                friendSum += newValue;
            } else {
                foes.add(centity);
                foeSum += newValue;
                if (entity.isCommander())
				 {
					newValue *= 3; // make bots like to attack commanders
				}
                if (newValue > maxFoeBv || maxFoe == null) {
                    maxFoeBv = newValue;
                    maxFoe = centity;
                }
                if (this.getEntitiesOwned().size() > 2) {
                    if (centity.strategy.target > 2) {
                        centity.strategy.target = 1 + .5 * (centity.strategy.target - 2);
                    }
                    if (percent < .85 && centity.strategy.target < maxModifier) {
                        centity.strategy.target *= (1.0 + 6 * numEntities);
                    } else if (percent < .95 && centity.strategy.target < maxModifier) {
                        centity.strategy.target *= (1.0 + 4 * numEntities);
                    } else if (percent <= 1) {
                        assignTargetbasedonPercent(numEntities, centity,
								percent);
                    }
                    // don't go below one
                    if (centity.strategy.target < 1) {
						centity.strategy.target = 1;
					}
                }
            }
        }
        System.out.println("Us " + friendSum + " Them " + foeSum);
        // do some more reasoning...
        double unitValues = friendSum;
        double enemyValues = foeSum;
        Iterator<CEntity> i = foes.iterator();

        if (friends.size() > 1) {
            if (Strategy.MainTarget == null
                    || null == game.getEntity(Strategy.MainTarget.getEntity().getId())) {
                Strategy.MainTarget = maxFoe;
            }
            if (null == Strategy.MainTarget) {
				System.err.println("TestBot#initMovement() - no main target for bot");
			} else if (null == Strategy.MainTarget.strategy) {
				System.err.println("TestBot#initMovement() - no strategy for main target");
			} else {
                Strategy.MainTarget.strategy.target += .2;
                while (i.hasNext()) {
                    CEntity centity = i.next();
                    // good turn, keep up the work, but randomize to reduce predictability
                    if (friendSum - foeSum >= .9 * unitValues - enemyValues) {
                        if (Compute.randomInt(2) == 1) {
                            centity.strategy.target += .3;
                        }
                        // lost that turn, but still in the fight, just get a
                        // little more aggressive
                    } else if (friendSum > .9 * foeSum) {
                        centity.strategy.target += .15;
                        // lost that turn and loosing
                    } else if (centity.strategy.target < 2) { // go for the
                        // gusto
                        centity.strategy.target += .3;
                    }
                    System.out.println(centity.getEntity().getShortName() + " "
                            + centity.strategy.target);
                }
            }
        }

        double ratio = friendSum / foeSum;
        double mod = 1;
        if (ratio < .9) {
            mod = .95;
        } 
        //else if (ratio < 1) {
            // no change
        //} 
        else { // attack
            mod = (1.0 + numEntities);
        }
        i = friends.iterator();
        while (i.hasNext()) {
            CEntity centity = i.next();
            if (!(mod < 1 && centity.strategy.attack < .6)
                    && !(mod > 1 && centity.strategy.attack >= maxModifier)) {
				centity.strategy.attack *= mod;
			}
        }
        System.gc(); // just to make sure
    }

	/**
	 * @param numEntities
	 * @param centity
	 * @param percent
	 */
	public void assignTargetbasedonPercent(double numEntities, CEntity centity,
			double percent) {
		if (percent == 1) {
		    centity.strategy.target /= (1.0 + 2 * numEntities);
		} else {
		    centity.strategy.target /= (1.0 + numEntities);
		}
	}

	/**
	 * @param numEntities
	 * @param centity
	 */
	public void assignStrAttack(double numEntities, CEntity centity) {
		if (centity.strategy.attack < 1) {
		    centity.strategy.attack = Math.min(1.4 * centity.strategy.attack, 1);
		} else {
		    centity.strategy.attack *= (1.0 + numEntities);
		}
	}

    /* 
     * @see megamek.client.bot.BotClient#processChat(megamek.common.event.GamePlayerChatEvent)
     */
    protected final void processChat(final GamePlayerChatEvent ge) {
        chatp.processChat(ge, this);
    }

    // Where do I put my units? This prioritizes hexes and facings
    /* 
     * @see megamek.client.bot.BotClient#calculateDeployment()
     */
    protected final void calculateDeployment() {

        int weaponCount;
        int hexCount, xAve, yAve, nDir;
        double avRange;

        Coords pointingTo = new Coords();
        Entity test_ent;
        Enumeration<Entity> validAttackers;

        int entNum = game.getFirstDeployableEntityNum();
        Coords[] cStart = getStartingCoordsArray();
        Coords cDeploy = getCoordsAround(getEntity(entNum), cStart);

        // Now that we have a location to deploy to, get a direction
        // Using average long range of deploying unit, point towards the largest
        // cluster of enemies in range

        avRange = 0.0;
        weaponCount = 0;
        for (Mounted mounted : getEntity(entNum).getWeaponList()) {
            WeaponType wtype = (WeaponType) mounted.getType();
            if ((wtype.getName() != "ATM 3") && (wtype.getName() != "ATM 6")
                    && (wtype.getName() != "ATM 9") && (wtype.getName().equals("ATM 12"))) {
                if (getEntity(entNum).getC3Master() != null) {
                    avRange += ((wtype.getLongRange()) * 1.25);
                } else {
                    avRange += wtype.getLongRange();
                }
                weaponCount++;
            }
        }
        for (Mounted mounted : getEntity(entNum).getAmmo()) {
            AmmoType atype = (AmmoType) mounted.getType();
            if (atype.getAmmoType() == AmmoType.T_ATM) {
                weaponCount++;
                avRange += 15.0;
                if (atype.getMunitionType() == AmmoType.M_HIGH_EXPLOSIVE) {
                    avRange -= 6;
                }
                if (atype.getMunitionType() == AmmoType.M_EXTENDED_RANGE) {
                    avRange += 12.0;
                }
            }
            if (atype.getAmmoType() == AmmoType.T_MML) {
                weaponCount++;
                if (atype.hasFlag(AmmoType.F_MML_LRM)) {
                    avRange = 9;
                } else {
                    avRange = 21.0;
                }
            }
        }

        avRange = avRange / weaponCount;

        hexCount = 0;
        xAve = 0;
        yAve = 0;
        validAttackers = game.getEntities();
        while (validAttackers.hasMoreElements()) {
            test_ent = validAttackers.nextElement();
            if (test_ent.isDeployed()) {
                if (test_ent.isVisibleToEnemy()) {
                    if (cDeploy.distance(test_ent.getPosition()) <= (int) avRange) {
                        hexCount++;
                        xAve += test_ent.getPosition().x;
                        yAve += test_ent.getPosition().y;
                    }
                }
            }
        }
        if (hexCount != 0) {
            pointingTo = new Coords((xAve / hexCount), (yAve / hexCount));
        } else {
            pointingTo = new Coords(game.getBoard().getWidth() / 2,
                    game.getBoard().getHeight() / 2);
        }
        nDir = cDeploy.direction(pointingTo);

        // If unit has stealth armor, turn it on
        if (getEntity(entNum) instanceof Mech
                && getEntity(entNum).getArmorType() == EquipmentType.T_ARMOR_STEALTH) {
            for (Mounted test_equip : getEntity(entNum).getMisc()) {
                MiscType test_type = (MiscType) test_equip.getType();
                if (test_type.hasFlag(MiscType.F_STEALTH)) {
                    if (test_equip.curMode().getName() != "On") {
                        test_equip.setMode("On");
                        super.sendModeChange(entNum, getEntity(entNum).getEquipmentNum(test_equip),
                                1);
                    }
                }
            }
        }

        Entity ce = game.getEntity(entNum);
        megamek.debug.Assert.assertTrue(!ce.isHexProhibited(game.getBoard().getHex(cDeploy)));
        deploy(entNum, cDeploy, nDir);
    }

    /* 
     * @see megamek.client.bot.BotClient#continueMovementFor(megamek.common.Entity)
     */
    protected final MovePath continueMovementFor(final Entity entity) {
        System.out.println("Contemplating movement of " + entity.getShortName() + " "
                + entity.getId());
        CEntity cen = centities.get(entity);
        cen.refresh();
        firstPass(cen);

        Object enemyArray[] = this.getEnemyEntities().toArray();
        MoveOption[] result = calculateMove(entity);
        MoveOption min = null;
        ArrayList<MoveOption[]> possible = new ArrayList<MoveOption[]>();
        boolean shortCircuit = false;

        if (result.length < 6) {
            min = result.length > 0 ? result[0] : null;
            shortCircuit = true;
        }
        possible.add(result);

        // should ignore mechs that are not engaged
        // and only do the below when there are 2 or mechs left to move
        if (!shortCircuit) {
            if (this.getEntitiesOwned().size() > 1) {
                GALance lance = new GALance(this, possible, 50, 80);
                lance.evolve();
                min = lance.getResult();
                this.oldMoves = lance;
            } else if (possible.get(0) != null && possible.get(0).length > 0) {
                min = possible.get(0)[0];
            }
        }
        if (min == null) {
            min = new MoveOption(game, centities.get(getFirstEntityNum()));
        }

        for (int d = 0; d < enemyArray.length; d++) {
            Entity en = (Entity) enemyArray[d];

            // ignore loaded units
            if (en.getPosition() == null) {
                continue;
            }

            CEntity enemy = centities.get(en);
            int enemy_hit_arc = CEntity.getThreatHitArc(enemy.current.getFinalCoords(),
                    enemy.current.getFinalFacing(), min.getFinalCoords());
            MoveOption.DamageInfo di = min.damageInfos.get(enemy);
            if (di != null) {
                enemy.expected_damage[enemy_hit_arc] += di.min_damage;
            }
            if (enemy.expected_damage[enemy_hit_arc] > 0) {
                enemy.hasTakenDamage = true;
            }
        }
        if (min.isPhysical) {
            centities.get(min.getPhysicalTargetId()).isPhysicalTarget = true;
        }
        System.out.println(min);
        min.getCEntity().current = min;
        min.getCEntity().last = min;
        this.myMechsMoved++;
        min.getCEntity().moved = true;

        // If this unit has a jammed RAC, and it has only walked,
        // add an unjam action
        if (min.getLastStep() != null) {
            if (min.getCEntity().entity.canUnjamRAC()) {
                if ((min.getLastStep().getMovementType() == IEntityMovementType.MOVE_WALK)
                        || (min.getLastStep().getMovementType() == IEntityMovementType.MOVE_VTOL_WALK)
                        || (min.getLastStep().getMovementType() == IEntityMovementType.MOVE_NONE)) {
                	unJamAction(min);        
                	}
            }
        }
        return min;
    }

    /* 
     * @see megamek.client.bot.BotClient#calculateMinefieldDeployment()
     */
    protected final Vector<Minefield> calculateMinefieldDeployment() {
        Vector<Minefield> deployedMinefields = new Vector<Minefield>();

        deployMinefields(deployedMinefields, getLocalPlayer().getNbrMFConventional(), 0);
        deployMinefields(deployedMinefields, getLocalPlayer().getNbrMFCommand(), 1);
        deployMinefields(deployedMinefields, getLocalPlayer().getNbrMFVibra(), 2);

        return deployedMinefields;
    }

    /* 
     * @see megamek.client.bot.BotClient#calculateArtyAutoHitHexes()
     */
    protected final PlayerIDandList<Coords> calculateArtyAutoHitHexes() {
        PlayerIDandList<Coords> artyAutoHitHexes = new PlayerIDandList<Coords>();
        artyAutoHitHexes.setPlayerID(this.getLocalPlayer().getId());
        return artyAutoHitHexes;
    }

    /**
     * Deploy minefields.
     *
     * @param deployedMinefields the deployed minefields
     * @param number the number
     * @param type the type
     */
    protected final void deployMinefields(final Vector<Minefield> deployedMinefields, final int number, final int type) {
        for (int i = 0; i < number; i++) {
            Coords coords = new Coords(Compute.randomInt(game.getBoard().getWidth()), Compute
                    .randomInt(game.getBoard().getHeight()));

            if (game.containsMinefield(coords)) {
                Minefield mineField = game.getMinefields(coords).get(0);
                if (mineField.getPlayerId() == getLocalPlayer().getId()) {
                    i--;
                    continue;
                }
            } else {
                Minefield mineField = null;

                if (type == 0) {
                    mineField = Minefield.createMinefield(coords, getLocalPlayer().getId(),
                            Minefield.TYPE_CONVENTIONAL, 10);
                } else if (type == 1) {
                    mineField = Minefield.createMinefield(coords, getLocalPlayer().getId(),
                            Minefield.TYPE_COMMAND_DETONATED, 10);
                } else if (type == 2) {
                    mineField = Minefield.createMinefield(coords, getLocalPlayer().getId(),
                            Minefield.TYPE_VIBRABOMB, 20);
                }
                deployedMinefields.add(mineField);
            }
        }
    }

    // Calculate the best location to aim at on a target Mech. Attack options
    // must match
    // 1:1 with WeaponAttackActions in Vector.

    /**
     * Gets the aim point.
     *
     * @param attackTree the attack_tree
     * @param atkActionList the atk_action_list
    
     */
    private void getAimPoint(final TreeSet<AttackOption> attackTree, final Vector<EntityAction> atkActionList) {

        if (attackTree == null || atkActionList == null) {
            return;
        }

        WeaponAttackAction aimedAttack;
        AttackOption currentOption;

        Vector<Integer> targetIdList; // List of viable aimed-shot targets

        // Adjusted damages
        double baseDamage, baseOdds;
        double refactoredDamage, refactoredHead;

        // Armor values
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        double[] values = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

        // Internal structure values
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        double[] isValues = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

        // Fitness values
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        double[] fitness = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

        // Counters for armor penetration
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        int[] penCounters = { 0, 0, 0, 0, 0, 0, 0, 0 };

        int attackerId, testTarget;
        int actionIndex = 0;

        // Base to-hit
        int baseToHit;

        // Best locations to aim for
        int bestLoc, bestLocHead;

        boolean hasTcomp = false;
        boolean imobTarget, rearShot;
        boolean isPrimaryTarget;

        // For each attack action

        targetIdList = new Vector<Integer>();
        for (EntityAction aea : atkActionList) {

            if (aea instanceof WeaponAttackAction) {
                // Get the attacker
                attackerId = ((WeaponAttackAction) atkActionList.get(0)).getEntityId();

                // Check to see if the attacker has a tcomp
                hasTcomp = game.getEntity(attackerId).hasTargComp();

                // Get the target entity id
                testTarget = ((WeaponAttackAction) aea).getTargetId();

                // If the target is a Mech
                if (game.getEntity(testTarget) instanceof Mech) {

                    // If the target is officially immobile or if the attacker has a tcomp
                    if (hasTcomp | (game.getEntity(testTarget).isImmobile())) {
                        if (!targetIdList.contains(testTarget)) {
                            targetIdList.add(testTarget);
                        }
                    }
                }
            }
        }

        // For each valid target
        isPrimaryTarget = true;
        for (Iterator<Integer> i = targetIdList.iterator(); i.hasNext();) {

            // Set the current target
            testTarget = i.next();
            imobTarget = game.getEntity(testTarget).isImmobile();

            // Get the targets aspect ratio
            rearShot = false;
            for (Iterator<AttackOption> j = attackTree.iterator(); j.hasNext();) {
                currentOption = j.next();
                if (currentOption.target.getEntity().getId() == testTarget) {
                    int attack_direction = currentOption.toHit.getSideTable();
                    if (attack_direction == ToHitData.SIDE_REAR) {
                        rearShot = true;
                    } else {
                        rearShot = false;
                    }
                    break;
                }
            }

            // Get the armor values for the target and make them negative (count up)
            values[0] = game.getEntity(testTarget).getArmor(Mech.LOC_HEAD);
            values[1] = game.getEntity(testTarget).getArmor(Mech.LOC_CT, rearShot);
            values[2] = game.getEntity(testTarget).getArmor(Mech.LOC_LT, rearShot);
            values[3] = game.getEntity(testTarget).getArmor(Mech.LOC_RT, rearShot);
            values[4] = game.getEntity(testTarget).getArmor(Mech.LOC_LARM);
            values[5] = game.getEntity(testTarget).getArmor(Mech.LOC_RARM);
            values[6] = game.getEntity(testTarget).getArmor(Mech.LOC_LLEG);
            values[7] = game.getEntity(testTarget).getArmor(Mech.LOC_RLEG);

            // Get the internals for the target
            isValues[0] = game.getEntity(testTarget).getInternal(Mech.LOC_HEAD);
            isValues[1] = game.getEntity(testTarget).getInternal(Mech.LOC_CT);
            isValues[2] = game.getEntity(testTarget).getInternal(Mech.LOC_LT);
            isValues[3] = game.getEntity(testTarget).getInternal(Mech.LOC_RT);
            isValues[4] = game.getEntity(testTarget).getInternal(Mech.LOC_LARM);
            isValues[5] = game.getEntity(testTarget).getInternal(Mech.LOC_RARM);
            isValues[6] = game.getEntity(testTarget).getInternal(Mech.LOC_LLEG);
            isValues[7] = game.getEntity(testTarget).getInternal(Mech.LOC_RLEG);

            // Reset the fitness array
            for (int arrIndex = 0; arrIndex < 8; arrIndex++) {
                fitness[arrIndex] = 0.0;
            }

            // Reset the penetration counter
            for (int arrIndex = 0; arrIndex < 8; arrIndex++) {
                penCounters[arrIndex] = 0;
            }

            // For each attack option
            actionIndex = 0;
            refactoredDamage = 0.0;
            refactoredHead = 0.0;

            bestLoc = Mech.LOC_CT;
            bestLocHead = Mech.LOC_CT;
            for (Iterator<AttackOption> j = attackTree.iterator(); j.hasNext();) {

                // If the target of the attack option is the current target
                currentOption = j.next();
                if (testTarget == currentOption.target.getEntity().getId()) {

                    // Get the weapon
                    Mounted testWeapon = currentOption.weapon;

                    // If the weapon is not LBX cannon or LBX cannon loaded with slug
                    boolean directFire = true;
                    if (((WeaponType) testWeapon.getType()).hasFlag(WeaponType.F_DIRECT_FIRE)) {
                        directFire = false;
                    }
                    if (testWeapon.getType().hasFlag(WeaponType.F_PULSE)) {
                        directFire = false;
                    }
                    if ((((WeaponType) testWeapon.getType()).getAmmoType() == AmmoType.T_AC_LBX)
                            || (((WeaponType) testWeapon.getType()).getAmmoType() == AmmoType.T_AC_LBX)) {
                        if (((AmmoType) testWeapon.getLinked().getType()).getAmmoType() == AmmoType.M_CLUSTER) {
                            directFire = false;
                        }
                    }
                    if (testWeapon.getCurrentShots() > 1) {
                        directFire = false;
                    }

                    // If the weapon is direct fire
                    if (directFire) {
                        // Get the expected damage, to-hit number, and odds
                        // (0-1) of hitting
                        baseDamage = isPrimaryTarget ? currentOption.primary_expected
                                : currentOption.expected;
                        baseToHit = isPrimaryTarget ? currentOption.toHit.getValue()
                                : currentOption.toHit.getValue() + 1;
                        baseOdds = isPrimaryTarget ? currentOption.primary_odds
                                : currentOption.odds;
                        baseDamage = baseOdds == 0.0 ? 0.0 : baseDamage / baseOdds;

                        // If the target is mobile, only a tcomp can make an aimed shot
                        if (!imobTarget & hasTcomp) {
                            // Refactor the expected damage to account for increased to-hit number

                            refactoredHead = 0.0;
                            if (baseToHit + 4 <= 12) {
                                refactoredDamage = baseDamage
                                        * (Compute.oddsAbove(baseToHit + 4) / 100.0);
                                ((WeaponAttackAction) atkActionList.get(actionIndex))
                                        .setAimingMode(IAimingModes.AIM_MODE_TARG_COMP);
                                // Consider that a regular shot has a roughly
                                // 20% chance of hitting the same location
                                // Use the better of the regular shot or aimed shot
                                if (0.2 * baseDamage * (Compute.oddsAbove(baseToHit) / 100.0) > refactoredDamage) {
                                    refactoredDamage = 0.2 * baseDamage
                                            * (Compute.oddsAbove(baseToHit) / 100.0);
                                    ((WeaponAttackAction) atkActionList.get(actionIndex))
                                            .setAimingMode(IAimingModes.AIM_MODE_NONE);
                                }
                            } else {
                                refactoredDamage = 0.0;
                                ((WeaponAttackAction) atkActionList.get(actionIndex))
                                        .setAimingMode(IAimingModes.AIM_MODE_NONE);
                            }
                        }

                        // If the target is immobile, the shot will always be aimed
                        if (imobTarget) {

                            // If the attacker has a tcomp, consider both  options: immobile aim, tcomp aim
                            if (hasTcomp) {

                                // Refactor the expected damage to account for
                                // increased to-hit number of the tcomp
                                refactoredDamage = baseDamage * (Compute.oddsAbove(baseToHit + 4) / 100.0);
                                refactoredHead = 0.0;
                                ((WeaponAttackAction) atkActionList.get(actionIndex))
                                        .setAimingMode(IAimingModes.AIM_MODE_TARG_COMP);

                                // Check against immobile aim mode w/tcomp assist
                                if (0.50 * baseDamage * (Compute.oddsAbove(baseToHit) / 100.0) > refactoredDamage) {
                                    refactoredDamage = 0.50 * baseDamage * (Compute.oddsAbove(baseToHit) / 100.0);
                                    refactoredHead = 0.50 * baseDamage * (Compute.oddsAbove(baseToHit + 7) / 100.0);
                                    ((WeaponAttackAction) atkActionList.get(actionIndex))
                                            .setAimingMode(IAimingModes.AIM_MODE_IMMOBILE);
                                }
                            } else {
                                // If the attacker doesn't have a tcomp, settle for immobile aim
                                refactoredDamage = 0.50 * baseDamage
                                        * (Compute.oddsAbove(baseToHit) / 100.0);
                                refactoredHead = 0.50 * baseDamage
                                        * (Compute.oddsAbove(baseToHit + 7) / 100.0);
                                ((WeaponAttackAction) atkActionList.get(actionIndex))
                                        .setAimingMode(IAimingModes.AIM_MODE_IMMOBILE);
                            }
                        }

                        // Count the refactored damage off each location. Count hits to IS.
                        // Ignore locations that have been previously destroyed
                        for (int arrIndex = 0; arrIndex < 8; arrIndex++) {
                            if (arrIndex == 0) {
                                values[arrIndex] -= refactoredHead;
                            } else {
                                values[arrIndex] -= refactoredDamage;
                            }
                            if (values[arrIndex] < 0 & isValues[arrIndex] > 0) {
                                isValues[arrIndex] += values[arrIndex];
                                values[arrIndex] = 0;
                                penCounters[arrIndex]++;
                            }
                        }
                    }// End if (AttackAction against current target)
                }
                actionIndex++;
            }

            double locMod;
            for (int arrIndex = 0; arrIndex < 8; arrIndex++) {
                locMod = 0.0;

                // If any location has had its armor stripped but is not
                // destroyed, criticals may result
                if (values[arrIndex] <= 0 & isValues[arrIndex] > 0) {
                    assignArmor(fitness, penCounters, testTarget, arrIndex);
                }

                // If any location has been destroyed, adjust the location value
                // relative to its value
                if (isValues[arrIndex] <= 0 & penCounters[arrIndex] > 0) {
                    assingFitness(fitness, testTarget, arrIndex);
                }

            }

            // Get the best target location, including the head
            refactoredDamage = fitness[1];
            for (int arrIndex = 0; arrIndex < 8; arrIndex++) {
                if (fitness[arrIndex] > refactoredDamage) {
                    refactoredDamage = fitness[arrIndex];
                    bestLocHead = assignBestLocHead(arrIndex);
                }
            }

            // Get the best target location, not including the head
            int tempIndex = 1;
            refactoredDamage = fitness[1];
            for (int arrIndex = 2; arrIndex < 8; arrIndex++) {
                if (fitness[arrIndex] > refactoredDamage) {
                    refactoredDamage = fitness[arrIndex];
                    tempIndex = arrIndex;
                    bestLoc = assignBestLoc(arrIndex);
                }
            }

            // For all weapon attack actions
            for (Iterator<EntityAction> j = atkActionList.iterator(); j.hasNext();) {
                aimedAttack = (WeaponAttackAction) j.next();

                // If the target of the action is the current target
                if (aimedAttack.getTargetId() == testTarget) {

                    // If the weapon aim mode is set to use a tcomp
                    if (aimedAttack.getAimingMode() == IAimingModes.AIM_MODE_TARG_COMP) {

                        // If the location is at least close to being breached
                        // or the target is immobile
                        if (values[tempIndex] <= Compute.randomInt(5)) {
                            aimedAttack.setAimedLocation(bestLoc);
                        } else {
                            aimedAttack.setAimingMode(IAimingModes.AIM_MODE_NONE);
                            aimedAttack.setAimedLocation(Entity.LOC_NONE);
                        }
                    }

                    // If the weapon aim mode is set for immobile aim
                    if (aimedAttack.getAimingMode() == IAimingModes.AIM_MODE_IMMOBILE) {
                        aimedAttack.setAimedLocation(bestLocHead);
                    }
                }
            }

            // Any targets after this are secondary targets. Use secondary odds
            // and damage.
            isPrimaryTarget = false;
        }
    }

	/**
     * Assign bestLoc.
     *
     * @param arrIndex the arrIndex
     * @return the int
     */
    public int assignBestLoc(int arrIndex) {
		int bestLoc;
		switch (arrIndex) {
		case 2: // case 1 is CT, which was set as default
		    bestLoc = Mech.LOC_LT;
		    break;
		case 3:
		    bestLoc = Mech.LOC_RT;
		    break;
		case 4:
		    bestLoc = Mech.LOC_LARM;
		    break;
		case 5:
		    bestLoc = Mech.LOC_RARM;
		    break;
		case 6:
		    bestLoc = Mech.LOC_LLEG;
		    break;
		case 7:
		    bestLoc = Mech.LOC_RLEG;
		    break;
		default:
		    bestLoc = Mech.LOC_CT;
		}
		return bestLoc;
	}

	/**
	 * Assign bestLocHead.
	 *
	 * @param arrIndex the arrIndex
	 * @return the int
	 */
	public int assignBestLocHead(int arrIndex) {
		int bestLocHead;
		switch (arrIndex) {
		case 0:
		    bestLocHead = Mech.LOC_HEAD;
		    break;
		case 2: // case 1 is CT, which was initialized as
		    // default
		    bestLocHead = Mech.LOC_LT;
		    break;
		case 3:
		    bestLocHead = Mech.LOC_RT;
		    break;
		case 4:
		    bestLocHead = Mech.LOC_LARM;
		    break;
		case 5:
		    bestLocHead = Mech.LOC_RARM;
		    break;
		case 6:
		    bestLocHead = Mech.LOC_LLEG;
		    break;
		case 7:
		    bestLocHead = Mech.LOC_RLEG;
		    break;
		default:
		    bestLocHead = Mech.LOC_CT;
		}
		return bestLocHead;
	}

	/**
	 * Assing fitness.
	 *
	 * @param fitness the fitness
	 * @param test_target the test_target
	 * @param arrIndex the arrIndex
	 */
	public void assingFitness(double[] fitness, int test_target, int arrIndex) {
		switch (arrIndex) {
		case 0: // Destroying the head is a hard kill and gets
		    // rid of the pilot, too
		    fitness[arrIndex] += 3 * getAimModifier(test_target, Mech.LOC_HEAD);
		    break;
		case 1: // Destroying the CT is a hard kill
		    fitness[arrIndex] += 2 * getAimModifier(test_target, Mech.LOC_CT);
		    break;
		case 2: // Destroying a side torso could be a soft kill
		    // or cripple
		    fitness[arrIndex] += 1.5 * getAimModifier(test_target, Mech.LOC_LT);
		    break;
		case 3:
		    fitness[arrIndex] += 1.5 * getAimModifier(test_target, Mech.LOC_RT);
		    break;
		case 6: // Destroying a leg is a mobility kill
		    fitness[arrIndex] += 1.5 * getAimModifier(test_target, Mech.LOC_LLEG);
		    break;
		case 7:
		    fitness[arrIndex] += 1.5 * getAimModifier(test_target, Mech.LOC_RLEG);
		    break;
		case 4: // Destroying an arm can cripple a Mech, but not
		    // the best option
		    fitness[arrIndex] += getAimModifier(test_target, Mech.LOC_LARM);
		    break;
		case 5:
		    fitness[arrIndex] += getAimModifier(test_target, Mech.LOC_RARM);
		    break;
		default:
			break;

		}
	}

	/**
	 * Assign armor.
	 *
	 * @param fitness the fitness
	 * @param pen_counters the pen_counters
	 * @param test_target the test_target
	 * @param arrIndex the arrIndex
	 */
	public void assignArmor(double[] fitness, int[] pen_counters,
			int test_target, int arrIndex) {
		double loc_mod;
		switch (arrIndex) {
		case 0: // Head hits are very good, pilot damage and
		    // critical systems
		    fitness[arrIndex] = 4.0 * pen_counters[arrIndex];
		    fitness[arrIndex] += getAimModifier(test_target, Mech.LOC_HEAD);
		    break;
		case 1: // CT hits are good, chances at hitting gyro,
		    // engine
		    fitness[arrIndex] = 3.0 * pen_counters[arrIndex];
		    fitness[arrIndex] += getAimModifier(test_target, Mech.LOC_CT);
		    break;
		case 2: // Side torso hits are good, equipment hits and
		    // ammo slots
		    loc_mod = getAimModifier(test_target, Mech.LOC_LT);
		    fitness[arrIndex] = 2.0 * pen_counters[arrIndex];
		    fitness[arrIndex] += loc_mod;
		    break;
		case 3:
		    loc_mod = getAimModifier(test_target, Mech.LOC_RT);
		    fitness[arrIndex] = 2.0 * pen_counters[arrIndex];
		    fitness[arrIndex] += loc_mod;
		    break;
		case 6: // Leg hits are good, reduces target mobility
		    loc_mod = getAimModifier(test_target, Mech.LOC_LLEG);
		    fitness[arrIndex] = 2.0 * pen_counters[arrIndex];
		    fitness[arrIndex] += loc_mod;
		    break;
		case 7:
		    loc_mod = getAimModifier(test_target, Mech.LOC_RLEG);
		    fitness[arrIndex] = 2.0 * pen_counters[arrIndex];
		    fitness[arrIndex] += loc_mod;
		    break;
		case 4: // Arm hits might damage some weapons, but not
		    // the best option
		    loc_mod = getAimModifier(test_target, Mech.LOC_LARM);
		    fitness[arrIndex] = pen_counters[arrIndex];
		    fitness[arrIndex] += loc_mod;
		    break;
		case 5:
		    loc_mod = getAimModifier(test_target, Mech.LOC_RARM);
		    fitness[arrIndex] = pen_counters[arrIndex];
		    fitness[arrIndex] += loc_mod;
		default:
			break;
		}
		
	}

    /**
     * Gets the aim modifier.
     ******
     * @param target_id the target_id
     * @param location the location
    
     * @return the aim modifier */
    private double getAimModifier(final int target_id, final int location) {
        double loc_total;
        // change the factor of 0.1 to float depending on critical item type
        loc_total = 0.1 * game.getEntity(target_id).getHittableCriticals(location);

        return loc_total;
    }

	@Override
	public void changePhase1(Phase phase) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void changePhase2(Phase phase) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void changePhase3(Phase phase) {
		// TODO Auto-generated method stub
		
	}
}

